package server;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import shared.BalanceData;
import shared.CollectionData;
import shared.Data;
import shared.TransactionData;

/**
 * Represent the account information database
 * 
 * @author Rasmus Stjerne, Joachim Lindholm, Daniel Gramstrup, Pamela Pedersen
 * 
 */

public class CCCSDatabase {

	private static CCCSDatabase instance = null;
	private int nextTransactionID = 1001;

	/**
	 * Singleton pattern implementation of the CCCSDatabase
	 * 
	 * @return return the same CCCSDatabase object every time a new request it.
	 */
	public static CCCSDatabase getInstance() {
		if (instance == null) {
			instance = new CCCSDatabase();
		}
		return instance;
	}

	/**
	 * HashMap is used to the store the accounts
	 */
	public Map<String, Account> accounts;

	/**
	 * CCCSDatabse constructor
	 */
	public CCCSDatabase() {
		// initialize the HashMap
		accounts = new HashMap<String, Account>();
		
		// Insert 3 test accounts into the database
		accounts.put("pape11ad",
				new Account("Vgkvgk>3", "Pamela Pedersen", 300));
		accounts.put("raha11ad", new Account("Dstact7?>>", "Rasmus Stjerne",
				200));
		accounts.put("joli11ae", new Account("Lia|?6", "Joachim Julius", 400));
	}

	/**
	 * retrieve Account object containing specified account name
	 * 
	 * @param accountName
	 * @return
	 */
	private Account getAccount(String accountName) {
		try {
			return accounts.get(accountName);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return null; // if no matching account was found, return null
	}

	/**
	 * XOR the input
	 * 
	 * @param input
	 *            clear text
	 * @return obfuscated input
	 */
	public String getCipher(String input) {
		return Data.xor(input);
	}

	/**
	 * Get the name of the Account
	 * 
	 * @param accountName
	 * @return name of the Account
	 */
	public String getName(String accountName) {
		return getAccount(accountName).getName();
	}

	/**
	 * Authentication of the User
	 * 
	 * @param accountName
	 * @param accountPassword
	 * @return true if the user exist in the database
	 */
	public boolean authenticateUser(String accountName, String accountPassword) {
		// attempt to retrieve the account with the account number
		Account userAccount = getAccount(accountName);
		// Cipher password
		String digestPassword = getCipher(accountPassword);
		// if account exists, return result of Account method validatePassword
		if (userAccount != null)
			return userAccount.validatePassword(digestPassword);
		else
			return false; // account number not found, so return false
	}

	/**
	 * return the full balance of Account with specified account name
	 * 
	 * @param accountName
	 * @return BalanceData object
	 */
	public BalanceData getBalance(String accountName) {
		BalanceData bd = new BalanceData();
		Account currentAccount = getAccount(accountName);

		bd.balance = currentAccount.getBalance();
		bd.pendingBalance = currentAccount.getPendingBalance();
		bd.totalBalance = currentAccount.getTotalBalance();

		return bd;
	}

	/**
	 * Insert CC to the Account
	 * 
	 * @param accountName
	 *            which user should recieve the CC
	 * @param amount
	 *            how many KM did the user obtain
	 */
	public void insert(String accountName, double amount) {
		getAccount(accountName).insert(amount);
	}

	/**
	 * User logout
	 * 
	 * @param accountName
	 */
	public void logout(String accountName) {
		this.getAccount(accountName).logout();
	}

	/**
	 * Spend CC from the Account
	 * 
	 * @param accountName
	 *            which user should use the CC
	 * @param amount
	 *            how many CC did the user spend
	 */
	public void spend(String accountName, double amount) {
		getAccount(accountName).spend(amount);
	}

	/**
	 * Transfer CC from two unique accounts
	 * 
	 * @param selection
	 *            obtained from the user
	 * @param recipient
	 *            of the transaction
	 */
	public void transfer(int selection, String recipient) {
		int transactionId = convertSelectionToID(selection, recipient);
		String sender = getAccount(recipient).getTransaction(transactionId)
				.getSender();
		// withdraw CC from sender
		getAccount(sender).accept(transactionId);
		// add CC to recipient
		getAccount(recipient).accept(transactionId);
	}

	/**
	 * Server method used to convert a user selection into a specific
	 * transaction id
	 * 
	 * @param selection
	 *            obtained from the user
	 * @param recipient
	 *            of the transaction
	 * @return the specific transaction id equivalent of the selection
	 */
	public int convertSelectionToID(int selection, String recipient) {
		// First obtain all the pending transactions related to the recipient
		ArrayList<Transaction> pendingTransaction = getAccount(recipient)
				.getPendingTransactions(recipient);
		// Initialize the transaction id, returns -1 if error occurs
		int transactionId = -1;
		// Look thru all pending transactions
		for (int i = 0; i < pendingTransaction.size(); i++) {
			// i corresponds to the current pending transaction, if i equals
			// selection the return the transaction id of that object.
			if (i == selection) {
				transactionId = pendingTransaction.get(i).getID();
			}
		}
		return transactionId;
	}

	/**
	 * Decline a specific transaction
	 * 
	 * @param selection
	 *            obtained from the user
	 * @param sender
	 *            of the transaction
	 */
	public void decline(int selection, String sender) {
		int transactionId = convertSelectionToID(selection, sender);
		String recipient = getAccount(sender).getTransaction(transactionId)
				.getRecipient();
		getAccount(sender).decline(transactionId);
		getAccount(recipient).decline(transactionId);
	}

	/**
	 * Add new transaction to both the recipient and the sender with a specified
	 * amount.
	 * 
	 * @param recipient
	 *            Obtained from the user
	 * @param sender
	 *            the current active user
	 * @param amount
	 *            Obtained from the user The transaction id is determined by the
	 *            system by incrementing the nextTransactionID field
	 */
	public void addTransaction(String recipient, String sender, double amount) {
		Transaction claim = new Transaction(sender, recipient, amount,
				++this.nextTransactionID);
		getAccount(recipient).addTransaction(claim);
		// The sender transaction needs a negative amount
		Transaction payables = new Transaction(sender, recipient, -amount,
				this.nextTransactionID);
		getAccount(sender).addTransaction(payables);
	}

	/**
	 * Get the transaction limit
	 * 
	 * @param accountName
	 * @return if the pending balance < 0 then return the total balance other
	 *         return the current balance.
	 */
	public double getTransactionLimit(String accountName) {
		BalanceData bd = getBalance(accountName);
		if (bd.pendingBalance < 0) {
			return bd.totalBalance;
		} else {
			return bd.balance;
		}
	}

	/**
	 * Get all pending transactions for the specific recipient
	 * 
	 * @param recipient
	 *            Obtained from the current user
	 * @return pending transaction list
	 */
	public CollectionData getPendingTransactions(String recipient) {
		CollectionData cd = new CollectionData();
		cd.pendingTransactions = new ArrayList<TransactionData>();
		for (Transaction t : getAccount(recipient).getPendingTransactions(
				recipient)) {
			TransactionData temp = new TransactionData();
			temp.sender = t.getSender();
			temp.recipient = t.getRecipient();
			temp.transferAmount = t.getAmount();
			temp.name = getName(t.getSender());
			cd.pendingTransactions.add(temp);
		}
		return cd;
	}

	/**
	 * Control whether the specified user exist in the database
	 * 
	 * @param accountName
	 *            obtained from the user
	 * @return true if the user exist otherwise false
	 */
	public boolean checkAccountExists(String accountName) {
		if (accounts.containsKey(accountName))
			return true;
		return false;
	}
}